package com.jiangyg.mall.coupon.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jiangyg.mall.core.support.restful.Pagination;
import com.jiangyg.mall.core.support.restful.Result;
import com.jiangyg.mall.core.utils.Logger;
import com.jiangyg.mall.coupon.constant.CacheConstant;
import com.jiangyg.mall.coupon.constant.ColumnConstant;
import com.jiangyg.mall.coupon.dao.HomeAdvDao;
import com.jiangyg.mall.coupon.dto.HomeAdvDTO;
import com.jiangyg.mall.coupon.entity.HomeAdv;
import com.jiangyg.mall.coupon.vo.HomeAdvVO;
import com.jiangyg.mall.db.mybatis.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

@Slf4j
@Service
public class HomeAdvServiceImpl extends BaseServiceImpl<HomeAdvDao, HomeAdv> implements HomeAdvService {

    /**
     * Redis缓存操作模板
     */
    private final RedisTemplate<String, Object> redisTemplate;

    final HashOperations<String, Object, Object> hashOperations;

    /**
     * 事物管理器
     */
    private final DataSourceTransactionManager transactionManager;

    @Autowired
    public HomeAdvServiceImpl(HomeAdvDao homeAdvDao,
                              RedisTemplate<String, Object> redisTemplate,
                              DataSourceTransactionManager transactionManager) {
        super(homeAdvDao);
        this.redisTemplate = redisTemplate;
        this.transactionManager = transactionManager;
        // TODO hashOperations 考虑替换单例
        this.hashOperations = redisTemplate.opsForHash();
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public Pagination<HomeAdvVO> selectPagination(Pagination<HomeAdvVO> pagination) throws Exception {
        return this.executeSelectPagination(pagination, HomeAdvVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdate(HomeAdvDTO dto) throws Exception {
        return this.executeSaveOrUpdate(dto) > 0;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public HomeAdvVO selectById(Long homeAdvId) throws Exception {
        return this.executeSelectById(homeAdvId, HomeAdvVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(List<Long> homeAdvIds) {
        return this.baseMapper.deleteBatchIds(homeAdvIds) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean switchStatus(HomeAdvDTO dto) {
        final HomeAdv adv = new HomeAdv();
        adv.setId(dto.getId());
        adv.setStatus(dto.getStatus());
        return this.baseMapper.updateById(adv) > 0;
    }

    @Override
    public String selectListByCache() {
        return (String) redisTemplate.opsForValue().get(CacheConstant.HOME_ADV_DATA_KEY);
    }

    @Override
    public void loadCache() {
        loadHomeAdvCache();
    }

    @Override
    public void click(Long homeAdvId) {
        // TODO 思考：需要判断 homeAdvId 是否存在吗？
        incrementClickCount(homeAdvId, 1L);
    }

    @Override
    public void clickCountDataPersistent() {
        // 1. 获取事务定义，设置事务隔离级别（新事务）
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        // 2. 扫描缓存中点击信息，进行持久化操作
        scanHomeAdvClickCacheConsumer((homeAdvId, clickCount) -> {
            Logger.debug(log, () -> String.format("[持久化首页广告点击次数]-从缓存提取广告[%d]点击次数[%d]，准备持久化……", homeAdvId, clickCount));
            // 2.1 如果点击次数小于等于0则本次循环不做任何处理
            if (clickCount <= 0) {
                Logger.debug(log, () -> String.format("[持久化首页广告点击次数]-广告[%d]点击次数为0，跳过本次处理……", homeAdvId));
                return;
            }
            // 2.2 如果点击次数大于0则在新的数据库事物中，先更新点击次数再把缓存中次数减去，这样就完成当前广告的点击次数持久化
            TransactionStatus status = null;
            try {
                status = transactionManager.getTransaction(def);
                this.baseMapper.updateClickCount(homeAdvId, clickCount);
                incrementClickCount(homeAdvId, -clickCount);
                transactionManager.commit(status);
                Logger.debug(log, () -> String.format("[持久化首页广告点击次数]-广告[%d]点击次数持久化成功！", homeAdvId));
            } catch (Exception ex) {
                Logger.error(log, () -> String.format("[持久化首页广告点击次数]-广告[%d]点击次数持久化失败", homeAdvId), ex);
                if (status != null) {
                    transactionManager.rollback(status);
                }
            }
        });
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public void cleanInvalidClickCountCache() {
        // 1. 扫描缓存中点击信息，清除已失效的缓存
        scanHomeAdvClickCacheConsumer((homeAdvId, clickCount) -> {
            Logger.debug(log, () -> String.format("[清除无效的广告点击缓存]-从缓存提取广告[%d]点击次数[%d]，准备进行清除……", homeAdvId, clickCount));
            // 1.1 如果点击次数大于0则本次循环不做任何处理
            if (clickCount > 0) {
                Logger.debug(log, () -> String.format("[清除无效的广告点击缓存]-广告[%d]点击次数大于0，跳过本次处理……", homeAdvId));
                return;
            }
            // 1.2 如果点击次数小于等于0且广告已失效则删除缓存
            final HomeAdv adv = this.baseMapper.selectById(homeAdvId);
            if (adv != null && adv.getEndTime().after(new Date())) {
                Logger.debug(log, () -> String.format("[清除无效的广告点击缓存]-广告[%d]未失效，跳过本次处理……", homeAdvId));
                return;
            }
            hashOperations.delete(CacheConstant.HOME_ADV_CLICK_KEY, String.valueOf(homeAdvId));
            Logger.debug(log, () -> String.format("[清除无效的广告点击缓存]-广告[%d]已失效，缓存已删除！", homeAdvId));
        });
    }

    /**
     * 功能描述：广告点击次数自增
     *
     * @param homeAdvId  广告ID
     * @param clickCount 点击次数
     */
    private void incrementClickCount(Long homeAdvId, Long clickCount) {
        hashOperations.increment(CacheConstant.HOME_ADV_CLICK_KEY, String.valueOf(homeAdvId), -clickCount);
    }

    /**
     * 功能描述：扫描首页广告点击信息处理
     *
     * @param consumer 消费事件
     */
    private void scanHomeAdvClickCacheConsumer(BiConsumer<Long, Long> consumer) {
        // 1. 批量从缓存中提取广告点击次数，为防止阻塞，每次提取10条数据
        final ScanOptions options = ScanOptions.scanOptions().count(10).build();
        final Cursor<Map.Entry<Object, Object>> cursor = hashOperations.scan(CacheConstant.HOME_ADV_CLICK_KEY, options);
        // 2. 遍历提取到的广告点击次数，进行消费操作
        while (cursor.hasNext()) {
            // 2.1 获取当前广告的点击信息
            final Map.Entry<Object, Object> entry = cursor.next();
            final Long homeAdvId = Long.parseLong(entry.getKey().toString());
            final Long clickCount = Long.parseLong(entry.getValue().toString());
            // 2.2 消费广告的点击信息
            consumer.accept(homeAdvId, clickCount);
        }
    }

    /**
     * 功能描述：加载首页广告缓存
     */
    private void loadHomeAdvCache() {
        final List<HomeAdv> homeAdvList = new ArrayList<>();
        // 1. 构建 状态=上线 且 未过期 的查询条件
        final QueryWrapper<HomeAdv> wrapper = new QueryWrapper<HomeAdv>();
        wrapper.eq(ColumnConstant.STATUS, 1);
        wrapper.gt(ColumnConstant.END_TIME, new Date());
        wrapper.orderByAsc(ColumnConstant.SORT);
        // 2. 查询首页大图列表
        {
            // TODO 可提取配置，动态缓存条数
            wrapper.eq(ColumnConstant.TYPE, 1);
            wrapper.last("limit 15");
            homeAdvList.addAll(this.baseMapper.selectList(wrapper));
        }
        // 3. 查询首页小图列表
        {
            // TODO 可提取配置，动态缓存条数
            wrapper.eq(ColumnConstant.TYPE, 2);
            wrapper.last("limit 18");
            homeAdvList.addAll(this.baseMapper.selectList(wrapper));
        }
        // 4. 如果不存在有效广告则退出
        if (CollectionUtils.isEmpty(homeAdvList)) {
            return;
        }
        // 5. 生成首页广告缓存
        List<Map<String, Object>> dataList = homeAdvList.stream().map(adv -> {
            Map<String, Object> data = new HashMap<>();
            data.put("id", String.valueOf(adv.getId()));
            data.put("name", adv.getName());
            data.put("pic", adv.getPic());
            data.put("url", adv.getUrl());
            data.put("type", adv.getType());
            return data;
        }).collect(Collectors.toList());
        redisTemplate.opsForValue().set(CacheConstant.HOME_ADV_DATA_KEY, Result.ok(dataList).toJSONString());
    }

}
